/* *****************************************************************************
 * Copyright (c) 2009, 2010 Andrey Litvinov
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * ****************************************************************************/
/** @file
 * This is a test application, which generates poll requests to the oBIX server.
 * It can emulate several device adapters who publish some data to the oBIX
 * server and then start polling it. There are three types of poll requests and
 * also a possibility to emulate writing requests, which normal device adapters
 * do in order to update device state at the server.
 * The configuration of the emulator is performed using command line arguments
 * and XML configuration file. The whole list of available command line
 * arguments can be found by launching poll generator with no arguments. The XML
 * configuration template can be found at res/poll_generator_config.xml
 *
 * @author Andrey Litvinov
 * @version 1.0
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>

#include <obix_client.h>
#include <xml_config.h>
#include <log_utils.h>
#include <ixml_ext.h>
#include <ptask.h>
#include <obix_utils.h>

#define USAGE_MESSAGE "\n" \
	"Usage:\n" \
	"   poll_generator params config_file\n" \
	"where\n" \
	" config_file - Name of the configuration file\n" \
	" params = -d<count> [-p<delay> | -P<rate>] -t[r|s|l<min_d>]\n" \
	"          [-w<delay> | -W<rate>] [-f<count>]\n" \
	"\n" \
	"Obligatory parameters:\n" \
	" -d<count>:\t Number of devices;\n" \
	" -p or -P: \t Define frequency of poll requests:\n" \
	"           \t -p  - Interval between poll requests for each device;\n" \
	"           \t -P  - Total request rate per second generated by the\n" \
	"           \t       application;\n" \
	" -t[r|s|l]:\t Type of generated poll requests:\n" \
	"           \t -tr - Simple read requests;\n" \
	"           \t -ts - Traditional \'short\' polling;\n" \
	"           \t -tl - Long polling; <min_d> - minimum poll waiting time;\n" \
	"\n" \
	"Optional parameters:\n" \
	" -w or -W: \t Enable emulation of writing requests:\n" \
	"           \t -w  - Interval between writing requests;\n" \
	"           \t -W  - Total writing request rate per second generated\n" \
	"           \t       by the application;\n" \
	" -f<count>:\t Number of fields which will be polled by each device\n" \
	"           \t (default value is %d).\n" \
	"\n" \
	"Example:\n" \
	"   poll_generator -d5 -P5 -ts config.xml\n" \
	" This command will launch emulation of 5 devices with total poll\n" \
	" request rate = 5 requests per second (just each device will poll\n" \
	" changes from server once in a second).\n" \
	" Polling mode = Traditional 'short' polling (using Watch.pollChanges\n" \
	" operation).\n" \
	" Connection settings will be loaded from \'confix.xml\' file.\n"

#define POLL_FIELDS_PER_DEVICE_DEFAULT 10

typedef enum {
    POLL_T_NONE,
    POLL_T_READ,
    POLL_T_SHORT,
    POLL_T_LONG
} POLL_TYPE;

char* _configFile = NULL;
long _deviceCount = 0;
long _pollIntervalPerDevice = 0;
long _writeIntervalPerDevice = 0;
long _pollFieldsPerDevice = 0;
POLL_TYPE _pollType = POLL_T_NONE;
long _longPollMinInterval = 0;
long _deviceId = 0;

int* _connectionIds;
IXML_Element* _deviceXML;
Task_Thread* _readThread = NULL;
Task_Thread* _statisticsThread = NULL;

pthread_mutex_t _pollCounterMutex = PTHREAD_MUTEX_INITIALIZER;
long _pollCounter = 0;
long _pollErrorCounter = 0;
pthread_mutex_t _writeCounterMutex = PTHREAD_MUTEX_INITIALIZER;
long _writeCounter = 0;
long _writeErrorCounter = 0;
long _executionTime = 0;


BOOL _shutDown = FALSE;

BOOL parseLong(long* output, char* argument, int startPos)
{
    if (*output != 0)
    {
        printf("Duplicate argument \"%s\" is found.\n", argument);
        return FALSE;
    }

    // try to parse the next argument
    *output = atol(argument + startPos);
    if (*output <= 0)
    {
        // cut the wrong part of the argument
        argument[startPos] = '\0';
        printf("Argument \"%s\" must be followed with a valid integer "
               "value, which should be greater than 0.\n", argument);
        return FALSE;
    }

    return TRUE;
}

POLL_TYPE parseAndSetPollType(char* argument)
{
    if (_pollType != POLL_T_NONE)
    {
        printf("Polling type should be specified only once "
               "(\"-r\"; \"-s\" or \"-l\").\n");
        return POLL_T_NONE;
    }

    if (strlen(argument) < 3)
    {	// the argument should be '-t'+ 1 character which defines the poll type
        printf("Unknown argument: %s", argument);
        return POLL_T_NONE;
    }

    POLL_TYPE type;

    switch(argument[2])
    {
    case 'r': // Poll data using simple read requests
        type = POLL_T_READ;
        break;
    case 's': // Poll data using short polling
        type = POLL_T_SHORT;
        break;
    case 'l': // Poll data using long polling
        type = POLL_T_LONG;
        break;
    default:
        {
            printf("Unknown argument: %s", argument);
            return POLL_T_NONE;
        }
        break;
    }

    //set pollType
    _pollType = type;
    return type;
}

/**
 * Checks that we have parsed all required arguments and that there is no
 * conflict between them.
 */
int checkParsedArguments(long pollPerSecond, long writePerSecond)
{
    if (_pollFieldsPerDevice == 0)
    {
        // use default value
        _pollFieldsPerDevice = POLL_FIELDS_PER_DEVICE_DEFAULT;
    }

    if (pollPerSecond > 0)
    {
        if (_pollIntervalPerDevice > 0)
        {
            printf("Both polling interval for each device (\"-p\") and "
                   "polling rate per second (\"-P\") can't be provided "
                   "simultaneously.\n");
            return -1;
        }

        _pollIntervalPerDevice = (long) (1000.0 / pollPerSecond * _deviceCount);
        if (_pollType == POLL_T_READ)
        {	// when we use simple read requests for polling, we poll each
            // field using one request. Otherwise, device issue one request for
            // all fields.
            _pollIntervalPerDevice *= _pollFieldsPerDevice;
        }
    }

    if (writePerSecond > 0)
    {
        if (_writeIntervalPerDevice > 0)
        {
            printf("Both writing interval for each device (\"-w\") and "
                   "writing request rate per second (\"-W\") can't be "
                   "provided simultaneously.\n");
            return -1;
        }

        _writeIntervalPerDevice =
            (long) (1000.0 / writePerSecond * _deviceCount);
    }

    // now check that the we have enough arguments
    if (_configFile == NULL)
    {
        printf("Configuration file is not specified.\n");
        return -1;
    }

    FILE* testFile = fopen(_configFile, "r");
    if (testFile == NULL)
    {
        printf("Wrong configuration file name: %s\n", _configFile);
        return -1;
    }
    fclose(testFile);

    if (_deviceCount == 0)
    {
        printf("Number of devices is not specified (\"-d\").\n");
        return -1;
    }

    if (_pollIntervalPerDevice == 0)
    {
        printf("Either polling interval for each device (\"-p\") or "
               "polling rate per second (\"-P\") must be specified.\n");
        return -1;
    }

    if (_pollType == POLL_T_NONE)
    {
        printf("Polling type is not specified.\n");
        return -1;
    }

    if ((_deviceCount > 1) && (_pollType == POLL_T_LONG))
    {
        printf("Unfortunately one instance of Poll Generator can emulate only "
               "one device with long polling feature. Please, run several "
               "Poll Generators simultaneously.\n");
        return -1;
    }

    return 0;
}

/**
 * Parses input arguments.
 *
 * @param argumentsCount Number of arguments.
 * @param arguments Array of arguments.
 * @return @li @a 0 if parsing is complete successfully;
 * 		   @li @a -1 on error.
 */
int parseInputArguments(int argumentsCount, char** arguments)
{
    if (argumentsCount == 1)
    {
        printf("Program cannot be launched without arguments.\n");
        return -1;
    }

    long pollPerSecond = 0;
    long writePerSecond = 0;

    // always consider the last argument as a config file
    _configFile = arguments[--argumentsCount];

    // parse the rest of arguments
    int i;
    for (i = 1; i < argumentsCount; i++)
    {
        char* arg = arguments[i];

        if (*arg != '-')
        {
            printf("Unknown argument: %s\n", arg);
            return -1;
        }

        // check the first letter after '-'
        switch (arg[1])
        {
        case 'd': // Number of devices
            if (!parseLong(&_deviceCount, arg, 2))
            {	// parsing failed
                return -1;
            }
            break;
        case 'p': // Poll interval for each device
            if (!parseLong(&_pollIntervalPerDevice, arg, 2))
            {	// parsing failed
                return -1;
            }
            break;
        case 'P': // Number of poll requests per second
            if (!parseLong(&pollPerSecond, arg, 2))
            {	// parsing failed
                return -1;
            }
            break;
        case 'w': // Writing request interval for each device
            if (!parseLong(&_writeIntervalPerDevice, arg, 2))
            {	// parsing failed
                return -1;
            }
            break;
        case 'W': // Number of writing requests per second
            if (!parseLong(&writePerSecond, arg, 2))
            {	// parsing failed
                return -1;
            }
            break;
        case 't': // Poll request type
            {
                POLL_TYPE type = parseAndSetPollType(arg);
                if (type == POLL_T_NONE)
                {
                    return -1;
                }

                if (type == POLL_T_LONG)
                {
                    // long polling should be also followed with integer value
                    if (!parseLong(&_longPollMinInterval, arg, 3))
                    {
                        return -1;
                    }
                }
            }
            break;
        case 'f': // Number of fields polled by each device
            if (!parseLong(&_pollFieldsPerDevice, arg, 2))
            {	// parsing failed
                return -1;
            }
            break;
        case 'i': // Device ID shift (id of the first device which is registered
            // at the server by this poll generator (default is 0).
            if (!parseLong(&_deviceId, arg, 2))
            {	// parsing failed
                return -1;
            }
            break;
        default:
            {
                printf("Unknown argument: %s\n", arg);
                return -1;
            }
            break;
        }
    }

    // parsing is successfully completed
    return checkParsedArguments(pollPerSecond, writePerSecond);
}

char* getStringFromLong(const char* pattern, long value)
{
    // 10 bytes should be enough for any long value
    char* output = (char*) malloc(strlen(pattern) + 10);
    if (output == NULL)
    {
        log_error("Not enough memory.");
        return NULL;
    }
    sprintf(output, pattern, value);

    return output;
}

int ixmlElement_setIntegerAttribute(
    IXML_Element* element,
    const char* attrName,
    long attrValue)
{
    char* stringValue = getStringFromLong("%ld", attrValue);
    if (stringValue == NULL)
    {
        return -1;
    }
    int error = ixmlElement_setAttribute(element, attrName, stringValue);
    free(stringValue);
    if (error != IXML_SUCCESS)
    {
        return -1;
    }

    return 0;
}

int createChildTagWithIntegerValue(IXML_Element* parentTag,
                                   const char* tagName,
                                   long tagValue)
{
    IXML_Element* childTag =
        ixmlElement_createChildElementWithLog(parentTag,
                                              tagName);
    if (childTag == NULL)
    {
        return -1;
    }

    ixmlElement_setIntegerAttribute(childTag, CTA_VALUE, tagValue);

    return 0;
}

int doForEachDevice(int (*task)(int deviceId))
{
    int i;
    for (i = 0; i < _deviceCount; i++)
    {
        int error = (*task)(i);
        if (error != 0)
        {
            return error;
        }
    }

    return 0;
}

int generateConnectionConfig(IXML_Element* configXML)
{
    // config file has one connection tag
    // we generate connection tag for each virtual device
    int error = 0;

    IXML_Element* connectionTag =
        config_getChildTag(configXML, "connection", TRUE);
    if (connectionTag == NULL)
    {
        return -1;
    }

    // set polling settings
    if (_pollType == POLL_T_SHORT)
    {
        error = createChildTagWithIntegerValue(connectionTag,
                                               "poll-interval",
                                               _pollIntervalPerDevice);
    }
    else if (_pollType == POLL_T_LONG)
    {
        IXML_Element* longPollTag =
            ixmlElement_createChildElementWithLog(connectionTag, "long-poll");
        if (longPollTag == NULL)
        {
            return -1;
        }
        error = createChildTagWithIntegerValue(longPollTag,
                                               "min-interval",
                                               _longPollMinInterval);
        error += createChildTagWithIntegerValue(longPollTag,
                                                "max-interval",
                                                _pollIntervalPerDevice);
    }
    if (error != 0)
    {
        log_error("Unable to generate XML with polling settings.");
        return error;
    }

    // set max listeners parameter
    IXML_Element* maxListenersTag =
        config_getChildTag(connectionTag, "max-listeners", TRUE);
    if (maxListenersTag == NULL)
    {
        return -1;
    }
    error = ixmlElement_setIntegerAttribute(maxListenersTag,
                                            CTA_VALUE,
                                            _pollFieldsPerDevice);
    if (error != 0)
    {
        return error;
    }

    // clone connection tags: 1 tag per each device
    int deviceId;
    for (deviceId = 1; deviceId < _deviceCount; deviceId++)
    {
        IXML_Node* clonedNode =
            ixmlNode_cloneNode(ixmlElement_getNode(connectionTag), TRUE);

        ixmlNode_appendChild(ixmlElement_getNode(configXML), clonedNode);
        // update it's id attribute
        error = ixmlElement_setIntegerAttribute(
                    ixmlNode_convertToElement(clonedNode),
                    "id",
                    deviceId);
        if (error != 0)
        {
            return error;
        }
    }
    return 0;
}

int extractDeviceData(IXML_Element* configXML)
{
    // load device data which will be posted to the oBIX server by each device
    _deviceXML =
        config_getChildTag(config_getChildTag(configXML, "device-info", TRUE),
                           "obj",
                           TRUE);
    if (_deviceXML == NULL)
    {
        return -1;
    }
    // clone this data because it should be available after config file is
    // closed
    _deviceXML = ixmlElement_cloneWithLog(_deviceXML, TRUE);
    if (_deviceXML == NULL)
    {
        return -1;
    }
    return 0;
}

/**
 * Loads configuration file, generates XML for the oBIX Client Library and
 * initializes it.
 */
int loadConfigFile()
{
    IXML_Element* configXML = config_loadFile(_configFile);
    if (configXML == NULL)
    {
        return -1;
    }

    int error = config_log(configXML);
    if (error != 0)
    {
        config_finishInit(configXML, FALSE);
        return error;
    }

    // initialize oBIX client library with connection settings
    error = generateConnectionConfig(configXML);
    if (error != 0)
    {
        config_finishInit(configXML, FALSE);
        return error;
    }

    error = obix_loadConfig(configXML);
    if (error != OBIX_SUCCESS)
    {
        config_finishInit(configXML, FALSE);
        return error;
    }

    error = extractDeviceData(configXML);
    if (error != 0)
    {
        config_finishInit(configXML, FALSE);
        return error;
    }

    config_finishInit(configXML, TRUE);
    return 0;
}

char* getDeviceData(int deviceId)
{
    // generate and set device URI
    char* deviceUri =
        getStringFromLong("/obix/TestDevice%d/", deviceId + _deviceId);
    if (deviceUri == NULL)
    {
        return NULL;
    }
    int error = ixmlElement_setAttribute(_deviceXML,
                                         OBIX_ATTR_HREF,
                                         deviceUri);
    free(deviceUri);
    if (error != IXML_SUCCESS)
    {
        log_error("Unable to generate device data.");
        return NULL;
    }

    return ixmlPrintNode(ixmlElement_getNode(_deviceXML));
}

int openConnection(int deviceId)
{
    _connectionIds[deviceId] = deviceId;
    int error = obix_openConnection(_connectionIds[deviceId]);
    if (error != 0)
    {
        log_error("Unable to establish connection with oBIX server.");
        return error;
    }
    return 0;
}

int openConnections()
{
    // open one connection per device
    _connectionIds = (int*) malloc(sizeof(int) * _deviceCount);
    if (_connectionIds == NULL)
    {
        log_error("Not enough memory.");
        return -1;
    }
    return doForEachDevice(&openConnection);
}

int signUpDevice(int deviceId)
{
    char* deviceData = getDeviceData(deviceId);
    if (deviceData == NULL)
    {
        return -1;
    }

    int error = obix_registerDevice(deviceId, deviceData);
    free(deviceData);
    if (error < 0)
    {
        log_error("Unable to register device at oBIX server.");
        return error;
    }

    return 0;
}

int signUpDevices()
{
    int error = doForEachDevice(&signUpDevice);
    if (error == 0)
    {
        printf("%ld devices are successfully registered at the oBIX server!\n",
               _deviceCount);
    }
    return error;
}

int pollListener(int connectionId,
                 int deviceId,
                 int listenerId,
                 const char* newValue)
{
    log_debug("Device #%d: Parameter #%d is \"%s\".",
              connectionId, listenerId, newValue);
    return 0;
}

int createWatch(int connectionId)
{
    // oBIX Watch object is created when we subscribe a listener for some value.
    int i;
    for (i = 0; i < _pollFieldsPerDevice; i++)
    {
        char* paramUri = getStringFromLong("dummy/str5/int%d", i);
        if (paramUri == NULL)
        {
            return -1;
        }
        int listenerId = obix_registerListener(connectionId,
                                               1,
                                               paramUri,
                                               &pollListener);
        free(paramUri);
        if (listenerId < 0)
        {
            log_error("Unable to add parameter to the list of watch items.\n");
            return listenerId;
        }
    }

    return 0;
}

int startWatchPolling()
{
    return doForEachDevice(&createWatch);
}

void taskPeriodicRead(void* arg)
{
    int connectionId = *((int*)arg);
    int i;
    for (i = 0; i < _pollFieldsPerDevice; i++)
    {
        char* paramUri = getStringFromLong("dummy/str5/int%d", i);
        if (paramUri == NULL)
        {	// can't do anything else :(
            return;
        }
        char* paramValue;
        int error = obix_readValue(connectionId, 1, paramUri, &paramValue);
        if (error == OBIX_SUCCESS)
        {
            pollListener(connectionId, 1, i, paramValue);
            free(paramValue);
            pthread_mutex_lock(&_pollCounterMutex);
            _pollCounter++;
            pthread_mutex_unlock(&_pollCounterMutex);
        }
        else
        {
            log_error("Device #%d: Unable to read parameter \"%s\".", paramUri);
            pthread_mutex_lock(&_pollCounterMutex);
            _pollErrorCounter++;
            pthread_mutex_unlock(&_pollCounterMutex);
        }
        free(paramUri);
    }
}

int startThread(Task_Thread** thread)
{
    if (*thread != NULL)
    {
        // thread is already started
        return 0;
    }

    *thread = ptask_init();
    if (*thread == NULL)
    {
        log_error("Unable to start a new thread.");
        return -1;
    }
    return 0;
}

void stopThread(Task_Thread* thread)
{
    if (thread == NULL)
    {
        // thread is not started - nothing to stop
        return;
    }
    // try to cancel the first scheduling task. sometimes it helps to
    // stop thread faster
    ptask_cancel(thread, 1, FALSE);

    int error = ptask_dispose(thread, TRUE);
    if (error != 0)
    {
        log_error("Unable to stop the reading thread.");
    }
}

int scheduleReadTask(int deviceId)
{
    int taskId = ptask_schedule(_readThread,
                                &taskPeriodicRead,
                                (void*) &(_connectionIds[deviceId]),
                                _pollIntervalPerDevice,
                                EXECUTE_INDEFINITE);
    if (taskId < 0)
    {
        return -1;
    }

    return 0;
}

int startReadCycles()
{
    int error = startThread(&_readThread);
    if (error != 0)
    {
        return error;
    }

    return doForEachDevice(&scheduleReadTask);
}


void taskPeriodicWrite(void* arg)
{
    int connectionId = *((int*) arg);
    // generate random parameter id
    int paramId = rand() % _pollFieldsPerDevice;

    char* paramUri = getStringFromLong("dummy/str5/int%d", paramId);
    if (paramUri == NULL)
    {	// can't do anything else :(
        return;
    }

    char* newValue = getStringFromLong("%ld", rand());
    if (newValue == NULL)
    {	// can't proceed
        free(paramUri);
        return;
    }

    int error =
        obix_writeValue(connectionId, 1, paramUri, newValue, OBIX_T_INT);
    if (error != OBIX_SUCCESS)
    {
        log_error("Device #%d: Unable to write \"%s\" to parameter \"%s\".",
                  connectionId, newValue, paramUri);
        pthread_mutex_lock(&_writeCounterMutex);
        _writeErrorCounter++;
        pthread_mutex_unlock(&_writeCounterMutex);
    }
    else
    {
        log_debug("Device #%d: New value (\"%s\") is set to \"%s\".",
                  connectionId, newValue, paramUri);
        pthread_mutex_lock(&_writeCounterMutex);
        _writeCounter++;
        pthread_mutex_unlock(&_writeCounterMutex);
    }
    free(paramUri);
    free(newValue);
}

int scheduleWriteTask(int deviceId)
{
    int taskId = ptask_schedule(_readThread,
                                &taskPeriodicWrite,
                                (void*) (&_connectionIds[deviceId]),
                                _writeIntervalPerDevice,
                                EXECUTE_INDEFINITE);
    if (taskId < 0)
    {
        return -1;
    }
    return 0;
}

int startWriteCycle()
{
    srand(time(NULL));
    int error = startThread(&_readThread);
    if (error != 0)
    {
        return error;
    }

    return doForEachDevice(&scheduleWriteTask);
}

long getCounterGrowth(long* counter,
                      long* previousValue)
{
    long growth = *counter - *previousValue;
    *previousValue = *counter;
    return growth;
}

void taskShowCurrentRequestRate(void* arg)
{
    static long _previousPollCount = 0;
    static long _previousPollErrorCount = 0;
    static long _previousWriteCount = 0;
    static long _previousWriteErrorCount = 0;

    _executionTime++;
    printf("%ld:\t", _executionTime);

    pthread_mutex_lock(&_pollCounterMutex);
    long pollPerSecond = getCounterGrowth(&_pollCounter, &_previousPollCount);
    long pollErrorPerSecond =
        getCounterGrowth(&_pollErrorCounter, &_previousPollErrorCount);
    pthread_mutex_unlock(&_pollCounterMutex);

    printf("poll success (errors): %ld r/s (%ld);\t",
           pollPerSecond, pollErrorPerSecond);

    if (_writeIntervalPerDevice > 0)
    {
        // gather also writing statistics
        pthread_mutex_lock(&_writeCounterMutex);
        long writePerSecond =
            getCounterGrowth(&_writeCounter, &_previousWriteCount);
        long writeErrorPerSecond =
            getCounterGrowth(&_writeErrorCounter, &_previousWriteErrorCount);
        pthread_mutex_unlock(&_writeCounterMutex);
        printf("write: %ld (%ld);\ttotal: %ld (%ld)\n",
               writePerSecond,
               writeErrorPerSecond,
               pollPerSecond + writePerSecond,
               pollErrorPerSecond + writeErrorPerSecond);
    }
    else
    {
        printf("\n");
    }
}

int startStatisticsGathering()
{
    int error = startThread(&_statisticsThread);
    if (error != 0)
    {
        return error;
    }

    int taskId = ptask_schedule(_statisticsThread,
                                &taskShowCurrentRequestRate,
                                NULL,
                                1000,
                                EXECUTE_INDEFINITE);
    if (taskId < 0)
    {
        return -1;
    }

    return 0;
}

void showFinalStatistics()
{
    if (_pollType == POLL_T_READ)
    {
        // calculate request rate
        pthread_mutex_lock(&_pollCounterMutex);
        pthread_mutex_lock(&_writeCounterMutex);
        double pollRate = ((double)_pollCounter) / ((double)_executionTime);
        double writeRate = ((double)_writeCounter) / ((double)_executionTime);
        double totalRate = pollRate + writeRate;

        printf("Final statistics:\n");
        printf("           success  errors   aver.rate (per second)\n"
               "   poll  : %-9ld%-9ld%-9.2f\n"
               "   write : %-9ld%-9ld%-9.2f\n"
               "   total : %-9ld%-9ld%-9.2f\n",
               _pollCounter, _pollErrorCounter, pollRate,
               _writeCounter, _writeErrorCounter, writeRate,
               _pollCounter + _writeCounter,
               _pollErrorCounter + _writeErrorCounter, totalRate);

        printf("Execution time: %ld seconds.\n", _executionTime);
        pthread_mutex_unlock(&_pollCounterMutex);
        pthread_mutex_unlock(&_writeCounterMutex);
    }
}

void signalHandler(int signal)
{
    _shutDown = TRUE;
    printf("\nSignal %d is caught, terminating.\n", signal);
}

int main(int argumentsCount, char** arguments)
{
    printf("oBIX poll request generator v. 1.0\n");

    int error = parseInputArguments(argumentsCount, arguments);
    if (error != 0)
    {
        printf(USAGE_MESSAGE, POLL_FIELDS_PER_DEVICE_DEFAULT);
        return -1;
    }

    error = loadConfigFile();
    if (error != 0)
    {
        return error;
    }

    error = openConnections();
    if (error != 0)
    {
        return error;
    }

    error = signUpDevices();
    if (error != 0)
    {
        return error;
    }

    if (_pollType == POLL_T_READ)
    {
        // we can collect statistics about requests only when
        // we issue all of them manually ( == we do not use Watches)
        error = startReadCycles();
        error += startStatisticsGathering();
    }
    else
    {
        error = startWatchPolling();
    }
    if (error != 0)
    {
        return error;
    }

    if (_writeIntervalPerDevice != 0)
    {
        error = startWriteCycle();
        if (error != 0)
        {
            return error;
        }
    }

    // register signal handler
    signal(SIGINT, &signalHandler);
    printf("Poll Generator is started.\n\n"
           "Press Ctrl+C to stop...\n");

    // wait for signal SIGINT (Ctrl+C)
    while (_shutDown == FALSE)
    {
        sleep(10);
    }

    // shutdown gracefully
    stopThread(_readThread);
    stopThread(_statisticsThread);
    showFinalStatistics();

    // release all resources allocated by oBIX client library.
    // No need to close connection or unregister listener explicitly - it is
    // done automatically.
    return obix_dispose();
}
